﻿using Microsoft.Web.Administration;
using Microsoft.WindowsAzure.Diagnostics;
using Microsoft.WindowsAzure.ServiceRuntime;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Web;
using System.Xml;
using Wams.Admin;

namespace Wams.Server
{
    public interface IWamsApplication
    {
        string GetAppPoolName();
    }
    public class WamsServerSite : IWamsApplication
    {
        public int Id = 0;
        public string Name;
        
        public List<WamsServerBinding> Bindings = new List<WamsServerBinding>();
        public List<WamsVirtualPath> VirtualPaths = new List<WamsVirtualPath>();
        public WamsContent Content;

        /// <summary>
        /// Maintains state for this site.
        /// </summary>
        private WamsSiteState _state = null;
        public WamsSiteState State
        {
            get
            {
                if (_state == null) _state = new WamsSiteState(string.Format("{0}_{1}", WamsCore.InstanceState.DeploymentId, WamsCore.InstanceState.InstanceId), Id);
                return _state;
            }
            set { _state = value; }
        }



        #region Synchronisation
        /// <summary>
        /// Synchronises content related to this site.
        /// </summary>
        public void SyncContent()
        {
            Logger.Log(LogLevel.Info, "Site Sync Content Start: " + this.Name);
            this.State.Status = SiteStatus.ContentSyncInProgress;
            bool syncOk = true;

            if (Content != null && !Content.Synchronise()) syncOk = false;
            foreach (WamsVirtualPath vDir in VirtualPaths)
            {
                if (vDir.Content != null && !vDir.Content.Synchronise()) syncOk = false;
            }

            SetFolderPermissions();

            if (!syncOk) this.State.Status = SiteStatus.Error;
            else
            {
                Logger.Log(LogLevel.Info, "Site Sync Content OK: " + this.Name);
                this.State.LastContentSync = DateTime.Now;
                this.State.Status = SiteStatus.Active;
            }
        }

        /// <summary>
        /// Synchronises the site settings with IIS.
        /// </summary>
        public void SyncSettings()
        {
            Logger.Log(LogLevel.Info, "Sync Settings: " + this.Name);
            if (this.Bindings.Count == 0 || string.IsNullOrEmpty(this.Name))
            {
                Logger.Log(LogLevel.Info, "Skipping site (missing settings): " + this.Name);
                return;
            }
            try
            {
                this.State.Status = SiteStatus.SettingsSyncInProgress;

                using (ServerManager serverManager = new ServerManager())
                {
                    // Initialise basic Site Settings.
                    Site iisSite = serverManager.Sites[this.Name];
                    string siteRoot = string.Format("{0}\\empty_{1}", WamsCore.InstallFolder, this.Name);
                    if (this.Content != null) siteRoot = this.Content.InstallPath;

                    if (iisSite == null)
                    {
                        if (!System.IO.Directory.Exists(siteRoot)) Directory.CreateDirectory(siteRoot);
                        iisSite = serverManager.Sites.Add(this.Name, siteRoot, 80);
                    }
                    else iisSite.Applications["/"].VirtualDirectories["/"].PhysicalPath = siteRoot;

                    iisSite.ServerAutoStart = true;
                    iisSite.Applications[0]["preloadEnabled"] = true;

                    // Add diagnostic logging to sites
                    try
                    {
                        string basePath = string.Format("{0}LogFiles\\WamsWeb", RoleEnvironment.GetLocalResource("DiagnosticStore").RootPath); 
                        string logPath = string.Format("{0}\\W3SVC{1}", basePath, iisSite.Id);
                        iisSite.LogFile.Directory = basePath;
                        iisSite.LogFile.Period = LoggingRolloverPeriod.Hourly;
                        if (!Directory.Exists(logPath)) Directory.CreateDirectory(logPath);
                        if (!WamsCore.DiagnosticConfig.Directories.DataSources.Any(x => x.Path == logPath))
                        {
                            WamsCore.DiagnosticConfig.Directories.DataSources.Add(new DirectoryConfiguration()
                            {
                                Container = "wad-wams-iis-" + this.Name.ToLower().Replace(" ", "-").Replace(".","-"),
                                DirectoryQuotaInMB = 256,
                                Path = logPath
                            });
                            WamsCore.UpdateDiagnostics();
                        }
                    }
                    catch { }


                    // Load site bindings.
                    iisSite.Bindings.Clear();

                    foreach (WamsServerBinding binding in this.Bindings)
                    {
                        Binding iisBinding = iisSite.Bindings.CreateElement("binding");
                        iisBinding.BindingInformation = binding.BindingInformation;
                        string[] ipBindingParts = iisBinding.BindingInformation.Split(new char[] { ':' }, 3);
                        string ipBinding = ipBindingParts[0];
                        if (ipBinding == "*") ipBinding = WamsCore.RoleIP;
                        iisBinding.BindingInformation = string.Format("{0}:{1}:{2}", ipBinding, ipBindingParts[1], ipBindingParts[2]);

                        if (binding.IsHttps)
                        {
                            iisBinding.Protocol = "https";
                            iisBinding.CertificateHash = Utils.HexStringToByteArray(binding.CertificateHash.ToUpper());
                            iisBinding.CertificateStoreName = "My";
                        }
                        else iisBinding.Protocol = "http";

                        iisSite.Bindings.Add(iisBinding);
                    }

                    // Set site application pool.
                    ApplicationPool appPool = serverManager.ApplicationPools[this.GetAppPoolName()];
                    if (appPool == null)
                    {
                        appPool = serverManager.ApplicationPools.Add(this.GetAppPoolName());
                        appPool["startMode"] = "AlwaysRunning";
                        Logger.Log(string.Format("Created app pool {0} for site {1}", this.GetAppPoolName(), this.Name));
                    }
                    appPool.ProcessModel.IdentityType = ProcessModelIdentityType.ApplicationPoolIdentity;
                    appPool.ManagedPipelineMode = ManagedPipelineMode.Integrated;

                    iisSite.Applications["/"].ApplicationPoolName = this.GetAppPoolName();

                    foreach (VirtualDirectory oldVdir in iisSite.Applications["/"].VirtualDirectories.Where(x => x.Path != "/"))
                    { iisSite.Applications["/"].VirtualDirectories.Remove(oldVdir); }

                    // Set site virtual directories.
                    foreach (WamsVirtualPath vDir in VirtualPaths)
                    {
                        if (vDir is WamsVirtualApp)
                        {
                            Application app = iisSite.Applications.Add(vDir.VirtualPath, vDir.Content.InstallPath);
                            app["preloadEnabled"] = true;
                            app.ApplicationPoolName = ((WamsVirtualApp)vDir).GetAppPoolName();

                            ApplicationPool vDirAppPool = serverManager.ApplicationPools[app.ApplicationPoolName];
                            if (vDirAppPool == null)
                            {
                                Logger.Log(string.Format("Creating app pool {0} for virtual app ({1})", app.ApplicationPoolName, vDir.VirtualPath));
                                vDirAppPool = serverManager.ApplicationPools.Add(app.ApplicationPoolName);
                                vDirAppPool.ProcessModel.IdentityType = ProcessModelIdentityType.ApplicationPoolIdentity;
                                vDirAppPool.ManagedPipelineMode = ManagedPipelineMode.Integrated;
                            }
                        }
                        else iisSite.Applications["/"].VirtualDirectories.Add(vDir.VirtualPath, vDir.Content.InstallPath);
                    }

                    serverManager.CommitChanges();
                }

                SetFolderPermissions();

                // Finish sync!
                this.State.LastContentSync = DateTime.Now;
                this.State.Status = SiteStatus.Active;
            }
            catch (Exception ex)
            {
                this.State.Status = SiteStatus.Error;
                Logger.Log(LogLevel.Error, "Ex while syncing settings for " + this.Name + ":\r\n" + ex.ToString());
            }
        }
        #endregion

        /// <summary>
        /// Loads the site's configuration from the XML config file.
        /// </summary>
        /// <param name="item">A part of the XML config file that deals with this particular site.</param>
        public void Configure(WamsSite item)
        {
            // Set the site id.
            this.Id = item.Id;

            // If the name changed, we must delete it from IIS so it needs to be recreated under a different name.
            if (this.Name != null && this.Name != item.Name)
            {
                this.DeleteFromIIS();
            }
            this.Name = item.Name;

            // If the site already existed, but the new information has a different source, we must delete the old content. 
            // New content will be refreshed in the next content sync.
            if (this.Content != null && item.Source != this.Content.Source)
            {
                this.Content.Delete();
                this.Content = null;
            }
            if (this.Content == null && !string.IsNullOrEmpty(item.Source)) this.Content = new WamsContent(item.Source, this);

            // Just clear the old bindings, and use the new ones. They will be applied in the next IIS settings refresh.
            Bindings.Clear();
            foreach (WamsSiteBinding binding in item.Bindings)
            {
                WamsServerBinding wamsBinding = new WamsServerBinding(binding.BindingInfo);
                if (binding.BindingType == WamsBindingType.Https)
                {
                    wamsBinding.CertificateHash = binding.CertificateHash;
                    wamsBinding.IsHttps = true;
                }
                Bindings.Add(wamsBinding);
            }

            // Get a list of the old apps and paths to be used later.
            var configuredVirtualPaths = VirtualPaths.Where(x => x is WamsVirtualPath); 
            var configuredVirtualApps = VirtualPaths.Where(x => x is WamsVirtualApp);
            
            // Loop through the new settings. For every setting, we check if it existed. If it did not, create it.
            foreach (WamsSiteVirtualPath vDir in item.VirtualPaths)
            {
                WamsVirtualPath wamsVDir = null;
                if (vDir is WamsSiteVirtualDir)
                {
                    // Look through the configuredVirtualApps to see if this was already added.
                    foreach (var configuredVPath in configuredVirtualPaths)
                    {
                        if (configuredVPath.Content.Source == vDir.Source && configuredVPath.VirtualPath == vDir.Path)
                        {
                            wamsVDir = configuredVPath;
                            break;
                        }
                    }
                    if (wamsVDir == null)
                    {   // Item is new. Add.
                        wamsVDir = new WamsVirtualPath();
                        wamsVDir.Content = new WamsContent(vDir.Source, this);
                        VirtualPaths.Add(wamsVDir);
                    }
                }
                else if (vDir is WamsSiteVirtualApp)
                {
                    // Look through the configuredVirtualPaths to see if this was already added.
                    foreach (var configuredVApp in configuredVirtualApps)
                    {
                        if (configuredVApp.Content.Source == vDir.Source && configuredVApp.VirtualPath == vDir.Path && ((WamsVirtualApp)configuredVApp).AppPoolIsolation == ((WamsSiteVirtualApp)vDir).AppPoolIsolation)
                        {
                            wamsVDir = configuredVApp;
                            break;
                        }
                    }
                    if (wamsVDir == null)
                    {   // Item is new. Add.
                        wamsVDir = new WamsVirtualApp(this);
                        ((WamsVirtualApp)wamsVDir).AppPoolIsolation = ((WamsSiteVirtualApp)vDir).AppPoolIsolation;
                        wamsVDir.Content = new WamsContent(vDir.Source, (WamsVirtualApp)wamsVDir);
                        VirtualPaths.Add(wamsVDir);
                    }
                }
                else continue;

                wamsVDir.VirtualPath = vDir.Path;
            }

            // Delete old virtual paths that were not found in the new settings.
            List<WamsVirtualPath> pathsToDelete = new List<WamsVirtualPath>();
            foreach (var configuredVPath in VirtualPaths)
            {
                bool found = false;
                foreach (var itemVPath in item.VirtualPaths)
                {
                    if (itemVPath is WamsSiteVirtualDir)
                    {
                        if (itemVPath.Source == configuredVPath.Content.Source && itemVPath.Path == configuredVPath.VirtualPath)
                        { found = true; break; }
                    }
                    else if (itemVPath is WamsSiteVirtualApp)
                    {
                        if (itemVPath.Source == configuredVPath.Content.Source && itemVPath.Path == configuredVPath.VirtualPath && ((WamsSiteVirtualApp)itemVPath).AppPoolIsolation == ((WamsVirtualApp)configuredVPath).AppPoolIsolation)
                        { found = true; break; }
                    }
                }
                if (!found) pathsToDelete.Add(configuredVPath);
            }

            // Delete the old obsolete settings.
            foreach (var oldItem in pathsToDelete)
            {
                this.VirtualPaths.Remove(oldItem);
            }
        }


        /// <summary>
        /// Stops the IIS site.
        /// </summary>
        public void StopSite()
        {
            Logger.Log("Stopping site " + this.Name);
            using (ServerManager serverManager = new ServerManager())
            {
                Site iisSite = serverManager.Sites[this.Name];
                if (iisSite == null || iisSite.State != ObjectState.Started) return;
                iisSite.Stop();
            }
        }

        /// <summary>
        /// Starts the IIS site.
        /// </summary>
        public void StartSite()
        {
            Logger.Log("Starting site " + this.Name);
            using (ServerManager serverManager = new ServerManager())
            {
                Site iisSite = serverManager.Sites[this.Name];
                if (iisSite == null || iisSite.State != ObjectState.Stopped) return;
                iisSite.Start();
            }
        }
    
        public string GetAppPoolName()
        {
            return Name;
        }

        public void SetFolderPermissions()
        {
            // Setting site file permissions.
            if (Content != null) Content.SetFilePermissions("IIS AppPool", this.GetAppPoolName());
            foreach (WamsVirtualPath vDir in VirtualPaths)
            {
                if (vDir is WamsVirtualApp)
                {
                    // Give permissions to the app pool to both the virtual app path and the root path (for inheritance).
                    if (vDir.Content != null) vDir.Content.SetFilePermissions("IIS AppPool", ((WamsVirtualApp)vDir).GetAppPoolName());
                    if (Content != null) this.Content.SetFilePermissions("IIS AppPool", ((WamsVirtualApp)vDir).GetAppPoolName());
                }
                else if (vDir.Content != null) vDir.Content.SetFilePermissions("IIS AppPool", this.GetAppPoolName());
            }
        }

        public void DeleteFromIIS()
        {
            Logger.Log("Deleting site " + this.Name);
            using (ServerManager serverManager = new ServerManager())
            {
                Site iisSite = serverManager.Sites[this.Name];
                if (iisSite == null) return;
                serverManager.Sites.Remove(iisSite);
                serverManager.CommitChanges();
            }
        }

        public void DeleteContent()
        {
            foreach (var vDir in VirtualPaths) vDir.Content.Delete();
            this.Content.Delete();
        }

        public void Delete()
        {
            DeleteFromIIS();
            DeleteContent();
        }
    }
}